package org.ws.httphelper.support.fetcher;

import com.google.common.collect.Maps;
import okhttp3.ConnectionPool;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.logging.HttpLoggingInterceptor;
import org.apache.commons.collections4.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.ws.httphelper.exception.FetchException;
import org.ws.httphelper.model.config.ClientConfig;
import org.ws.httphelper.model.http.ContentType;
import org.ws.httphelper.model.http.HttpMethod;
import org.ws.httphelper.model.http.RequestData;
import org.ws.httphelper.model.http.ResponseData;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 使用OkHttpClient实现的fetcher
 */
public class OkHttpClientFetcher extends AbstractHttpFetcher{

    private static Logger log = LoggerFactory.getLogger(OkHttpClientFetcher.class.getName());

    /**
     * 当前客户端
     */
    private final OkHttpClient client;

    public OkHttpClientFetcher(ClientConfig clientConfig) {
        super(clientConfig);
        this.client = makeClientBuilder(clientConfig).build();
    }

    /**
     * 根据配置创建OkHttpClient
     * @param clientConfig 配置
     * @return
     */
    private OkHttpClient.Builder makeClientBuilder(ClientConfig clientConfig){

        OkHttpClient.Builder builder = new OkHttpClient.Builder()
                .retryOnConnectionFailure(true)
                .connectTimeout(clientConfig.getTimeout(), TimeUnit.MILLISECONDS)
                .callTimeout(clientConfig.getTimeout(), TimeUnit.MILLISECONDS)
                .readTimeout(clientConfig.getTimeout(), TimeUnit.MILLISECONDS)
                .writeTimeout(clientConfig.getTimeout(), TimeUnit.MILLISECONDS);
        builder.followRedirects(clientConfig.isFollowRedirects());
        // debug情况下启用log，影响性能
        if(log.isDebugEnabled()) {
            HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor(new OkHttpLog());
            logInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            builder.addInterceptor(logInterceptor);
        }
        if(clientConfig.isEnableCookie()){
            builder.cookieJar(new SimpleCookieJar());
        }
        // 多线程情况，使用okHttp的pool
        if(clientConfig.getThreadNumber() > 1){
            ConnectionPool pool = new ConnectionPool(clientConfig.getThreadNumber(), 3, TimeUnit.MINUTES);
            builder.connectionPool(pool);
        }
        else {
            ConnectionPool pool = new ConnectionPool(1, 3, TimeUnit.MINUTES);
            builder.connectionPool(pool);
        }
        if(clientConfig.isEnableSsl()){
            // TODO
            builder.followSslRedirects(clientConfig.isFollowRedirects());
        }


        return builder;
    }

    /**
     * 执行抓取
     * @param requestData
     * @return
     * @throws FetchException
     */
    @Override
    public ResponseData fetch(RequestData requestData) throws FetchException {
        try {
            // 根据requestData生成Request
            Request request = makeRequest(requestData);
            // 开始已经
            long startTime = System.currentTimeMillis();
            // 执行请求
            Response response = this.client.newCall(request).execute();
            // 生成响应
            return makeResponseData(response,startTime);
        } catch (IOException e) {
            log.error(e.getMessage(),e);
            throw new FetchException(e.getMessage(),e);
        }
    }

    /**
     * 生成响应数据
     * @param response
     * @param startTime
     * @return
     * @throws IOException
     */
    private ResponseData makeResponseData(Response response, long startTime) throws IOException {
        ResponseData responseData = new ResponseData();
        // 耗时
        responseData.setWasteTime(System.currentTimeMillis()-startTime);
        responseData.setStatus(response.code());
        boolean successful = response.isSuccessful();
        if(successful){
            ResponseBody body = response.body();
            // 存在无body和无content
            if(body != null && body.contentLength() != 0) {
                MediaType mediaType = body.contentType();
                // string类型的body
                if (mediaType != null && isStringBody(mediaType.toString())) {
                    responseData.setBody(body.string());
                } else {
                    // 其他为byte[]
                    responseData.setBody(body.bytes());
                }
            }
        }
        else {
            log.error("{} -> response status[{}] unsuccessful.",response.request().url(),response.code());
        }
        // 处理header
        Headers headers = response.headers();
        if(headers != null){
            int size = headers.size();
            Map<String, String> headersMap = Maps.newHashMapWithExpectedSize(size);
            for (int i = 0; i < size; i++) {
                String name = headers.name(i);
                String value = headers.value(i);
                headersMap.put(name,value);
            }
            responseData.setHeaders(headersMap);
        }
        return responseData;
    }

    /**
     * 生成请求
     * @param requestData
     * @return
     */
    private Request makeRequest(RequestData requestData){
        Request.Builder builder = new Request.Builder();
        builder.url(requestData.getUrl());
        Map<String, String> headers = requestData.getHeaders();
        if(MapUtils.isNotEmpty(headers)){
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                builder.addHeader(entry.getKey(),entry.getValue());
            }
        }
        HttpMethod method = requestData.getMethod();
        RequestBody body = makeRequestBody(requestData);
        switch (method){
            case GET: {
                builder.get();
                break;
            }
            case HEAD:{
                builder.head();
                break;
            }
            case POST:{
                builder.post(body);
                break;
            }
            case DELETE:{
                if(body == null){
                    builder.delete();
                }
                else {
                    builder.delete(body);
                }
                break;
            }
            case PUT:{
                builder.put(body);
                break;
            }
            case PATCH:{
                builder.patch(body);
                break;
            }
            default:
                break;
        }
        return builder.build();
    }

    /**
     * 生成body
     * @param data
     * @return
     */
    private RequestBody makeRequestBody(RequestData data){
        if(data.getBody() == null
                || data.getContentType() == ContentType.EMPTY) {
            return null;
        }
        ContentType type = data.getContentType();
        String charset = data.getCharset();
        Object body = data.getBody();
        MediaType mediaType = MediaType.parse(type.getType() + ";charset=" + charset);
        if(type != ContentType.BINARY) {
            return RequestBody.create(mediaType, (String)body);
        }
        else {
            return RequestBody.create(mediaType, (byte[]) body);
        }
    }

    /**
     * 日志
     */
    static class OkHttpLog implements HttpLoggingInterceptor.Logger {

        @Override
        public void log(String s) {
            if(log.isDebugEnabled()) {
                log.debug(s);
            }
        }
    }

    /**
     * Cookie
     */
    static class SimpleCookieJar implements CookieJar {

        private final List<Cookie> cookieList = new ArrayList<>();

        @Override
        public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
            cookieList.addAll(cookies);
        }

        @Override
        public List<Cookie> loadForRequest(HttpUrl url) {
            List<Cookie> result = new ArrayList<>();
            for (Cookie cookie : cookieList)
            {
                if (cookie != null && cookie.matches(url))
                {
                    result.add(cookie);
                }
            }
            return result;
        }
    }

}
